package mornd.test2;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author: mornd
 * @dateTime: 2023/5/7 - 17:47
 * 手动添加参数解析器解析目标方法
 */
public class App {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);

        // 获取模拟请求
        HttpServletRequest request =  getMockRequest();

        // 获取控制器方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, Controller1.User.class, Controller1.User.class, Controller1.User.class));

        // 用于存放参数的容器
        ModelAndViewContainer container = new ModelAndViewContainer();

        //  获取 controller1#test 方法的所有参数
        MethodParameter[] methodParameters = handlerMethod.getMethodParameters();

        // 1、请求的参数与方法上的参数绑定，2、进行类型转换 String->int
        ServletRequestDataBinderFactory binderFactory = new ServletRequestDataBinderFactory(null, null);

        // 遍历控制器方法上的所有参数
        for (MethodParameter p : methodParameters) {

            RequestParamMethodArgumentResolver requestParamResolver =
                    //  false 表示只解析标注了 @RequestParam 注解的参数
                    new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false);

            // 组合方式解析 （组合器-设计模式）
            // 将所有解析器添加到组合解析器中，待组合解析器一起执行
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                requestParamResolver, // @RequestParam
                new PathVariableMethodArgumentResolver(), // 解析@PathVariable
                new RequestHeaderMethodArgumentResolver(context.getBeanFactory()), // @RequestHeader
                new ServletCookieValueMethodArgumentResolver(context.getBeanFactory()), // @CookieValue
                new ExpressionValueMethodArgumentResolver(context.getBeanFactory()), // @Value
                new ServletRequestMethodArgumentResolver(), // 处理 HttpServletRequest 等参数
                // 构造器参数：注解是否不是必须的，true：不添加 @ModelAttribute 注解也会解析
                new ServletModelAttributeMethodProcessor(false), // @ModelAttribute
                new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())), // @RequestBody
                new ServletModelAttributeMethodProcessor(true) // spring 会添加两个这种解析器来解析
                //new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true) // 可以省略 @RequestParam 注解解析
            );

            Object result = null;
            if(composite.supportsParameter(p)) { // 是否有资格被解析，是否标注了指定的注解
                result = composite.resolveArgument(p, container, new ServletWebRequest(request), binderFactory);
            }

            String anno = Arrays.stream(p.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            // 参数名解析器
            p.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
            System.out.println("[" + p.getParameterIndex() + "]  "
                    + p.getParameterType().getSimpleName() + "  "
                    + (anno.length() > 0 ? "@" + anno : "") + "  "
                    + p.getParameterName() + " ==>" +  result
                    //+ p.getParameter().getName()
            );

        }
    }

    /**
     * 模拟请求
     * @return
     */
    private static HttpServletRequest getMockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "张三");
        request.setParameter("name2", "李四");
        request.setParameter("age", "20");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> uriTemplateVars = new AntPathMatcher()
                .extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
        request.setContentType("application/json");

        request.setCookies(new Cookie("token", "cookieToken...."));

        request.setParameter("id", "1001");
        request.setParameter("name", "王五");
        request.setContent("{\"id\":\"1002\",\"name\":\"何七\"}".getBytes(StandardCharsets.UTF_8));

        // 配合 @RequestParam("file") 文件上传时的解析
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }
}
